// @(#)MobilityModel.java   1/2004
// Copyright (c) 1998-2004, Distributed Real-time Computing Lab (DRCL) 
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer. 
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution. 
// 3. Neither the name of "DRCL" nor the names of its contributors may be used
//    to endorse or promote products derived from this software without specific
//    prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 

package drcl.inet.mac;

import drcl.comp.*;
import drcl.inet.data.*;
import drcl.net.*;

/**
* This class simulates the movement of the mobile nodes. The position of 
* the node is periodically updated and reported to the <code>NodePositionTracker</code>
* component. The coordinates can be either (longitude, lagitude, height) or (X, Y, Z).
* There are two different modes. If a trajectory array is installed, the mobility
* model assume the mobile node move from one point in the trajectory to the 
* next point at a constant speed. Otherwise starting from the original 
* position, the node randomly chooses a position in the simulated area as the destination,
* and moves in a straight line to that destiona point at a constant speed which is 
* uniformly distributed between 0 and Max_Speed. Whenever the node arrives at the destination,
* it chooses the next position and repeats the procedure again.  
*
* @see WirelessPhy
* @see NodePositionTracker
* @author Ye Ge
*/     
public class MobilityModel extends drcl.net.Module implements drcl.comp.ActiveComponent
{

  /** 
  * This field is defined to keep the node Id instead of requesting such kind of
  * information from other components which may incur extra simulation overhead.
  */
  public long nid;
  
  /** 
  * Sets the nid. 
  *
  *@param nid_ the id of the mobile node.
  */ 
  public void setNid(long nid_) { nid = nid_; }
  
  /** Gets the nid. */
  public long getNid() { return nid; }
  
  /* configurate the ports */
  private static final String REPORT_PORT_ID  = ".report";   // connect to the mobile nodes' wirelessphy   
  private static final String CONFIG_PORT_ID  = ".config";
  private static final String QUERY_PORT_ID   = ".query";    // connect to the channel component
  
  /** the port which should be connected to the mobility tracker. */
  protected Port reportPort = addPort(REPORT_PORT_ID, false);
  
  /** the config port */
  protected Port configPort = addPort(CONFIG_PORT_ID, false); 
  
  /** the query port which should be connected to the WireleePhy component. */
  protected Port queryPort  = addPort(QUERY_PORT_ID, false);
  
  // parameters same as those in NodePositionTracker
  
  /** The largest x coordinate value of the simulated area */     
  public double maxX;
  /** The largest y coordinate value of the simulated area */     
  public double maxY;
  /** The largest z coordinate value of the simulated area */     
  public double maxZ;
  /** The smallest x coordinate value of the simulated area */     
  public double minX;
  /** The smallest y coordinate value of the simulated area */     
  public double minY;
  /** The smallest z coordinate value of the simulated area */     
  public double minZ;
  /** The grid size along X-axle */
  public double dX;
  /** The grid size along Y-axle */
  public double dY;
  /** The grid size along Z-axle */
  public double dZ;    

  /** The x coordinator of the node's current position */ 
  public double X;
  /** The y coordinator of the node's current position */ 
  public double Y;
  /** The z coordinator of the node's current position */ 
  public double Z;                            // current position
  
  
  /** The x coordinate of the node's previous position */ 
  public double X0;
  /** The y coordinate of the node's previous position */ 
  public double Y0;
  /** the z coordinate of the node's previous position */ 
  public double Z0;                   // position at the last update time
  /** The x coordinate of the destination position */ 
  public double destX;
  /** The y coordinate of the destination position */ 
  public double destY;
  /** The z coordinate of the destination position */ 
  public double destZ;          // destination position       
  
  /** The moving speed along the x-axle */ 
  public double sx;
  /** The moving speed along the y-axle */ 
  public double sy;
  /** The moving speed along the z-axle */ 
  public double sz;
  /** The moving speed */        
  public double speed;            // (sx, sy, sz) is the normalized direction of the movement 
  
  /** The maximal speed for generating a random speed heading to the next random direction */
  public double Max_Speed;                    // Max_Speed for generating a random speed heading to the next random direction
  
  ACATimer reportPositionTimer;       // the object to keep the reference to the ACATimer so that we can cancel it if necessary
  ACATimer changeDestinationTimer;  

  /** The last time (X,Y,Z) is updated */
  private double positionUpdateTime;           // the last time (X,Y,Z) is updated
  /** The interval between automatic position updates */    
  private double positionReportPeriod;         // automatic position update period time
                    // position update may also be triggered by the query
  /** The time period between two destination points */
  double changeDestinationPeriod;      // the time between two destination points

  /** The index of the current starting position in the trajectory array. */
  int        nCurrentTraj;            // index of the current starting position  
  
  /**
  * Number of points on the trajectory.
  */
  int        nTrajectory;
  
  /**
  * An two dimensional array represents the trajectory of the mobile node.
  */
  double[][] trajectory;              // arrys of ( time, longitude,  latitude, altitude ) 

  
  /**
  * The random number generator. The default seed is set to 11111.
  */
  static java.util.Random rand = new java.util.Random(11111); 

  /** Installs a trajectory array. */
  public void installTrajectory(double[][] trajectory_)  {      // the trajectory point array derived from SDF file 
    trajectory   = trajectory_;             // set the trajectory ( arry of states in SDF file ) 
    nTrajectory  = trajectory.length;
    nCurrentTraj = -1;         
  }
  
  /** 
  * Constructor.
  *
  *@param nid_ the id of the mobile node.
  */
  public MobilityModel(long nid_) {
    super();
    nid = nid_;
    X  = 0.0; Y  = 0.0; Z  = 0.0;
    X0 = 0.0; Y0 = 0.0; Z0 = 0.0;
    sx = 0.0; sy = 0.0; sz = 0.0;
    speed = 0.0;
    queryPort.setType(drcl.comp.Port.PortType_SERVER);
    positionReportPeriod = 1.0;

    removeDefaultDownPort(); 
    removeDefaultUpPort();  
    
  }

  /**
  * Constructor.
  */
  public MobilityModel( ) {
    super();
    nid = -1;
    X  = 0.0; Y  = 0.0; Z  = 0.0;
    X0 = 0.0; Y0 = 0.0; Z0 = 0.0;
    sx = 0.0; sy = 0.0; sz = 0.0;
    speed = 0.0;
    queryPort.setType(drcl.comp.Port.PortType_SERVER);
    positionReportPeriod = 1.0;

    removeDefaultDownPort(); 
    removeDefaultUpPort();  

  }
  
  /**
  * Set topology parameters
  *
  * @param maxX_ largest x coordinate value of the simulated area
  * @param minX_ smallest x coordinate value of the simulated area
  * @param maxY_ largest y coordinate value of the simulated area
  * @param minY_ smallest y coordinate value of the simulated area
  * @param dX_   grid size along X-axle
  * @param dY_   grid size along Y-axle
  * @param dZ_   grid size along Z-axle
  */
  public void setTopologyParameters(double maxX_, double maxY_, double minX_, double minY_, double dX_, double dY_, double dZ_)  {
    maxX = maxX_; maxY = maxY_; maxZ = 10.0; 
    minX = minX_; minY = minY_; minZ = 0.0;
    dX = dX_;     dY = dY_;     dZ = dZ_;
  }    
  
  /**
  * Set topology parameters
  *
  * @param maxX_ largest x coordinate value of the simulated area
  * @param minX_ smallest x coordinate value of the simulated area
  * @param maxY_ largest y coordinate value of the simulated area
  * @param minY_ smallest y coordinate value of the simulated area
  * @param maxZ_ largest y coordinate value of the simulated area
  * @param minZ_ smallest y coordinate value of the simulated area
  * @param dX_   grid size along X-axle
  * @param dY_   grid size along Y-axle
  * @param dZ_   grid size along Z-axle
  */
  public void setTopologyParameters(double maxX_, double maxY_, double maxZ_, double minX_, double minY_, double minZ_, double dX_, double dY_, double dZ_)  {
    maxX = maxX_; maxY = maxY_; maxZ = maxZ_; 
    minX = minX_; minY = minY_; minZ = minZ_;
    dX   = dX_;   dY   = dY_;   dZ   = dZ_;
  }    

  /**
  * Sets the initial position of the node.
  * @param Max_Speed_ - maximum moving speed
  * @param X_ - initial X coordinate
  * @param Y_ - initial Y coordinate
  * @param Z_ - initial Z coordinate
  */
  public void setPosition(double Max_Speed_, double X_, double Y_, double Z_) {
    X = X_;  Y = Y_;  Z = Z_;
    X0 = X_; Y0 = Y_; Z0 = Z_;
    Max_Speed = Max_Speed_;        
  }  
  
  
  //public void putPosition() {
  //  debug("X=" + X + " Y=" + Y + "Z = " + Z);        
  //}
  
  protected void _start ()  {
    //randomPosition();

    //System.out.println("MobilityModel _start()");        
    if ( trajectory == null ) {     
      reportPosition(true);         // the position of the node must have been set in tcl file
      setRandomDestination();
      positionUpdateTime = getTime();
    }    
    else {
      setTrajectoryDestination();
      reportPosition(true);
      positionUpdateTime = getTime();
    }    
  }
  
  protected void _stop()  {
    if (reportPositionTimer    != null)  cancelTimeout(reportPositionTimer);
    if (changeDestinationTimer != null)  cancelTimeout(changeDestinationTimer);  
  }

  protected void _resume()  {   
    // Why comment out this?
    //if (reportPositionTimer == null)  updatePosition( );
  }    
  
  /**
  * Sets this mobile node to a random generated position.
  */ 
  public synchronized void setRandomPosition() {

    X = minX + rand.nextDouble() * ( maxX - minX );
    Y = minY + rand.nextDouble() * ( maxY - minY );
    Z = minZ + rand.nextDouble() * ( maxZ - minZ );        
        
    reportPosition(true);    // force it to report
    positionUpdateTime = getTime();       
  }
    
  /**
  * Sets a random generated destination to move to 
  */
  public synchronized void setRandomDestination( ) {
    double dist;
    
    updatePosition();    // calculate the current position
    
    // speed = Math.random() * Max_Speed;
    speed = rand.nextDouble() * Max_Speed;
    
    destX = minX + rand.nextDouble() * (maxX - minX);
    destY = minY + rand.nextDouble() * (maxY - minY);
    destZ = minZ + rand.nextDouble() * (maxZ - minZ);
    
    sx = destX - X;
    sy = destY - Y;
    sz = destZ - Z;
    
    dist = Math.sqrt((sx * sx) + (sy * sy) + (sz * sz));
    
    if ( destX != X || destY != Y || destZ != Z ) {
      sx = sx/dist*speed;
      sy = sy/dist*speed;
      sz = sz/dist*speed;
    }
    
    if ( speed > 1.0e-20 ) 
      changeDestinationPeriod = dist / speed;
    else
      changeDestinationPeriod = dist / 1.0e-20;
    
    // why cancel?
    //if ( changeDestinationTimer != null )  cancelTimeout(changeDestinationTimer); 
    changeDestinationTimer = setTimeout("ChangeDestination", changeDestinationPeriod);
    
  }

  /**
  * Sets the next destination according to the installed trajectory.
  */
  public synchronized void setTrajectoryDestination( ) {
    double dist;
    double now = getTime();

    if ( nCurrentTraj == -1 ) {   // first time calling, initiate
      nCurrentTraj = 0;
      X  = trajectory[0][1];  Y  = trajectory[0][2]; Z  = trajectory[0][3];
      X0 = trajectory[0][1];  Y0 = trajectory[0][2]; Z0 = trajectory[0][3];
    }    
    else {
      X = trajectory[nCurrentTraj][1];  Y = trajectory[nCurrentTraj][2]; Z = trajectory[nCurrentTraj][3];  // set the current position to the destination
    }    
    
    positionUpdateTime = now;

    if ( nCurrentTraj < (nTrajectory-1) ) {
      sx = (trajectory[nCurrentTraj+1][1] - trajectory[nCurrentTraj][1]);     // this is not direction yet
      sy = (trajectory[nCurrentTraj+1][2] - trajectory[nCurrentTraj][2]); 
      sz = (trajectory[nCurrentTraj+1][3] - trajectory[nCurrentTraj][3]);             
      dist = Math.sqrt(sx*sx + sy*sy + sz*sz);
      changeDestinationPeriod = (double) ( trajectory[nCurrentTraj+1][0] - trajectory[nCurrentTraj][0] );  // time to the next update time
      speed = dist / changeDestinationPeriod;                    
      sx = sx / changeDestinationPeriod;          //  velocity in x direction
      sy = sy / changeDestinationPeriod;
      sz = sz / changeDestinationPeriod;
      
      destX = trajectory[nCurrentTraj+1][1];
      destY = trajectory[nCurrentTraj+1][2];
      destZ = trajectory[nCurrentTraj+1][3];

      nCurrentTraj = nCurrentTraj + 1;
    
      //if ( changeDestinationTimer != null )  cancelTimeout(changeDestinationTimer); 
      changeDestinationTimer = setTimeout("ChangeDestination", changeDestinationPeriod);
    }
    else {
      dist = 0.0; speed = 0.0; 
      changeDestinationPeriod = 1000000000.0;      
      sx = 0.0; sy = 0.0; sz = 0.0;
      // no more trajectory update timeout event scheduled
    }    
  }    
  
  /**
  * Calculates the current position upon timeout or query
  */
  public synchronized void updatePosition() {       
    double now = getTime();
    double interval = now - positionUpdateTime;   // the time from now to the next timeout 
    
    if ( interval == 0.0 )  return;
    
    X = X + sx * interval;
    Y = Y + sy * interval;
    Z = Z + sz * interval;
    
    // adjust the position if it exceed the destination point 
    if ((sx > 0 && X > destX) || (sx < 0 && X < destX))  X = destX;   
    if ((sy > 0 && Y > destY) || (sy < 0 && Y < destY))  Y = destY;
    if ((sz > 0 && Z > destZ) || (sz < 0 && Z < destZ))  Z = destZ;
    
    
    positionUpdateTime = now;

    //debug("updatePosition X=" + X + " Y=" + Y);        
  }
  
  /**
  * Sends position report to the <code>NodePositionTracker</code> component.
  * If falseReport is set to true, the report is generated immediately.
  */
  public synchronized void reportPosition(boolean forcedReport) {
    if (forcedReport == true || (X-X0) >= dX || (X0-X) >= dX || (Y-Y0) >= dY || (Y0-Y) >= dY || (Z-Z0) >= dZ || (Z0-Z) >= dZ ) {   // if not forced report, report only if the change of position has exceeded the boundary
      reportPort.doSending(new PositionReportContract.Message(nid, X, Y, Z, X0, Y0, Z0));
      X0 = X; Y0 = Y; Z0 = Z;
    }    
  }    
    
  /**
  * Handles timeout events.
  */
  protected synchronized void timeout(Object data_) {
    if ( data_.equals("ChangeDestination") ) {
      if ( trajectory == null ) {
        setRandomDestination();
        reportPosition(false);
        // timeout is scheduled again in setRandomDestination
      }
      else {
        setTrajectoryDestination();
        reportPosition(false);
        // timeout is scheduled again in setTrajectoryDestination
      }    
    }
    else if ( data_.equals("ReportPosition") ) {   
      if ( trajectory == null ) {
        updatePosition();
        reportPosition(false);
        reportPositionTimer = setTimeout("ReportPosition", positionReportPeriod);
      }
      else {
        updatePosition();
        reportPosition(false);
        reportPositionTimer = setTimeout("ReportPosition", positionReportPeriod);
      }    
    }    
  }
  
  protected synchronized void processOther(Object data_, Port inPort_)  {
    String portid_ = inPort_.getID();
  
    if (portid_.equals(QUERY_PORT_ID)) 
      processQuery(data_);
    else 
      super.processOther(data_, inPort_);
  }  

  /**
  * Processes the position query from <code>WirelessPhy</code> component.
  */
  protected synchronized void processQuery(Object data_) {
    updatePosition();
    queryPort.doSending(new PositionReportContract.Message(X, Y, Z));
    
  }    

  /** 
  * Sets the seed of the random number generator.
  */
  public void setSeed(long seed) {
    rand.setSeed(seed);
  }
  
  public String info()
  {
    return "current=(" + X + "," + Y + "," + Z + ")\n"
      + "   prev=(" + X0 + "," + Y0 + "," + Z0 + ")\n"
      + "  speed=(" + sx + "," + sy + "," + sz + ")=" + speed + "\n"
      + " toward=(" + destX + "," + destY + "," + destZ + ")\n";
  }
}

